package se.webbzon.oschi01.filenameparser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * This class can be use to divide a string into a sequence of filenames from given directory.
 * The parsing supports three regular-expressions which can be used to define filenames.
 * 
 * @author Oscar Hinrichsen (oschi01)
 * 
 * ============================================================================
 * ========================== Usage information ===============================
 * ============================================================================
 * 
 * This class supports the following regular-expressions:
 * 
 * 1. Separator "|"
 * 
 *	   The separator can be used to separate indexes or filenames.
 *	   - Example 1:
 * 
 *	   "/home/user/docs/image.png|picture.png"
 * 
 *	   Will return:
 *	   { "/home/user/docs/image.png", "/home/user/docs/picture.png" }
 * 
 *	   - Example 2:
 * 
 *	   "/home/user/docs/image01|02"
 * 
 *	   Will return:
 *	   { "/home/user/docs/image01", "/home/user/docs/image02" }
 * 
 * 2. Range ">"
 * 
 *	   The range can be used to load a range of indexes.
 *	   - Example 1:
 * 
 *	   "/home/user/docs/image01>03"
 * 
 *	   Will return:
 *	   { "/home/user/docs/image01", "/home/user/docs/image02" , "/home/user/docs/image03"}
 * 
 *	   Note: The range be defined in increasing as well as decreasing order.
 * 
 * 3. Extension ":"
 * 
 *	   The extension regular-expression can be used to set a extension to a pattern.
 *	   - Example 1:
 * 
 *	   "/root/img01>03|02:.png"
 *
 *	   Will return:
 *	   { "/root/img01.png", "/root/img02.png", "/root/img03.png", "/root/img02.png"}
 *
 */
public class FilenameParser {
	
	/*============================================================
	Public Methods
	============================================================*/
	
	public static Collection<String> parse(String expression) {
		final List<String> content = new ArrayList<String>();
		final String[] pathArray = expression.split("/|\\\\");
		final StringBuilder pathBuilder = new StringBuilder();
		for (int n = 0; n < pathArray.length-1 ; n++)
			pathBuilder.append(pathArray[n] + "/");
		
		final List<String> basenames = parseExpression(pathArray[pathArray.length-1]);
		final String path = pathBuilder.toString();
		for (String basename : basenames)
			content.add(path + basename);
		
		return Collections.unmodifiableList(content);
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns a linked list of basenames parsed from an expression */
	private static List<String> parseExpression(String expression) {
		final String[] operationArray = expression.split("[^>:|]");
		final String[] contentArray = expression.split("[>:|]");
		final List<String> operationList = new ArrayList<String>();
		final List<String> contentList = new ArrayList<String>();
		
		for (int n = 0; n < operationArray.length; n++)
			if (operationArray[n].length() > 0)
		operationList.add(operationArray[n]);
		
		for (int n = 0; n < contentArray.length; n++)
			contentList.add(contentArray[n]);
	
		if (contentList.size() - operationList.size() == 1)
			return buildBasenames(contentList,operationList);
		else
			return null;
	}
	
	/** Builds up a linked list of basenames from an operation list {|,>,:} and a content list (strings) */
	private static List<String> buildBasenames(List<String> content, List<String> operations) {
		final List<String> extensionlessResult = new ArrayList<String>();
		final List<String> result = new ArrayList<String>();
		String basename = "", tempBasename, lastIndex = "", index, operation = "|";
	
		while (true) {
			if (content.isEmpty()) {
				result.addAll(extensionlessResult);
				return result;
			}
		
			tempBasename = getBasenameName(content.get(0));
			if (tempBasename.length() > 0)
				basename = tempBasename;
			index = getBasenameIndex(content.get(0));
			content.remove(0);

			if (operation.equals(">")) {
				extensionlessResult.addAll(createRange(basename,lastIndex,index));
			} else if (operation.equals("|")) {
				extensionlessResult.add(basename + index);
			} else if (operation.equals(":")) {
				for(String str : extensionlessResult)
					result.add(str + basename + index);
				extensionlessResult.clear();
			}
		
			lastIndex = index;
			if (operations.isEmpty()) {
				result.addAll(extensionlessResult);
				return result;
			}
			operation = operations.get(0);
			operations.remove(0);
		}
	}
	
	/** Returns a range of basenames from firstIndex to lastIndex.
	 * Example: createRange("image", "01", "03") --> ( "image01", "image02", "image03") */
	private static List<String> createRange(String basename, String firstIndex, String lastIndex) {
		final int first = new Integer(firstIndex);
		final int last = new Integer(lastIndex);
		final int length = Math.max(firstIndex.length(), lastIndex.length());
		final List<String> range = new ArrayList<String>();
		
		if (first < last)
			for (int n = first+1; n <= last; n++)
				range.add(basename + String.format("%0"+length+"d", n));
		else if (first > last)
			for (int n = first-1; n >= last; n--)
				range.add(basename + String.format("%0"+length+"d", n));
		return range;
	}
	
	/** Returns the index of a basename. 
	 * Example getIndex("picture02") -> "02" */
	private static String getBasenameIndex(String str) {
		final StringBuilder basename = new StringBuilder();
		
		char character;
		for (int n = str.length()-1; n >= 0; n--) {
			character = str.charAt(n);
			if (Character.isDigit(character))
				basename.insert(0, character);
			else
				return basename.toString();
		}
		return basename.toString();
	}
	
	/** Returns the name of a basename (without index). 
	 * Example getIndex("picture02") -> "picture" */
	private static String getBasenameName(String str) {
		final StringBuilder basename = new StringBuilder();
		
		char character;
		boolean start = false;
		for (int n = str.length()-1; n >= 0; n--) {
			character = str.charAt(n);
			if (!Character.isDigit(character)) {
				start = true;
				basename.insert(0, character);
			}
			else if (start)
				basename.insert(0, character);
		}
		return basename.toString();
	}
	
}